package com.whz.governance.redislimiter.command;

import com.google.common.collect.Maps;
import com.whz.generic.spring.SpringUtils;
import com.whz.governance.redislimiter.RedisConstants;
import com.whz.governance.redislimiter.tool.RedisScriptExecutor;
import com.whz.governance.redislimiter.tool.RedisScriptUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author whz
 */
public abstract class BaseCommandExecutor<T> {

    static Map<Type, Type> classTypeCache = Maps.newConcurrentMap();

    private final Map<String, RedisScriptExecutor<T>> cache = Maps.newConcurrentMap();

    private final Class<T> type;

    @SuppressWarnings("unchecked")
    protected BaseCommandExecutor() {
        Type superClass = getClass().getGenericSuperclass();
        Type type = ((ParameterizedType) superClass).getActualTypeArguments()[0];
        Type cachedType = classTypeCache.get(type);
        if (cachedType == null) {
            classTypeCache.putIfAbsent(type, type);
            cachedType = classTypeCache.get(type);
        }
        this.type = (Class<T>) ((ParameterizedType) cachedType).getRawType();
    }

    public RedisScriptExecutor<T> getRedisScriptExecutor(String fileName) {
        return cache.computeIfAbsent(fileName,
                                     key -> new RedisScriptExecutor<>(RedisScriptUtils.readRedisScript(fileName, type),
                                                                      SpringUtils.getBean(StringRedisTemplate.class)
                                                                                 .orElseThrow(
                                                                                         () -> new RuntimeException(
                                                                                                 "StringRedisTemplate Bean Not Found."))));
    }

    /**
     * execute the lua redis command
     *
     * @param params the command params
     * @return the command executed results
     */
    public T execute(ScriptParams params) {
        String scriptFileName = getScriptFileName();
        Assert.hasText(scriptFileName, "scriptFileName cannot be blank.");
        // key加前缀
        List<String> keys =
                params.getKeys().stream().flatMap(k -> decorateKey(k).stream()).collect(Collectors.toList());
        // 执行命令
        return getRedisScriptExecutor(scriptFileName).executeScript(keys, params.getArgs());
    }

    /**
     * 默认的装饰key的方法,简单的加了前缀
     */
    protected List<String> decorateKey(String key) {
        String commandPrefix = getKeyPrefix();
        if (!StringUtils.hasText(commandPrefix)) {
            commandPrefix = "command:";
        }
        commandPrefix = commandPrefix.trim();
        if (!commandPrefix.endsWith(":")) {
            commandPrefix = commandPrefix + ":";
        }

        return Collections.singletonList(RedisConstants.PREFIX + commandPrefix + key);
    }

    /**
     * 命令关键词key的统一前缀
     *
     * @return tongyu:common:tool:'your prefix':key
     */
    protected abstract String getKeyPrefix();

    /**
     * lua script file name
     *
     * @return fileName
     */
    protected abstract String getScriptFileName();
}